package com.yonyougov.devops.cruxcisupport.k8s.rest.service.impl;

import com.yonyougov.devops.cruxcisupport.app.AppEntity;
import com.yonyougov.devops.cruxcisupport.app.AppService;
import com.yonyougov.devops.cruxcisupport.appenv.AppEnvService;
import com.yonyougov.devops.cruxcisupport.appenvconfigitem.AppEnvConfigItemEntity;
import com.yonyougov.devops.cruxcisupport.appenvconfigitem.AppEnvConfigItemService;
import com.yonyougov.devops.cruxcisupport.appenvport.AppEnvPortEntity;
import com.yonyougov.devops.cruxcisupport.appenvport.AppEnvPortService;
import com.yonyougov.devops.cruxcisupport.deployhistory.DeployHisService;
import com.yonyougov.devops.cruxcisupport.deployhistory.dto.DeployHisRecordDto;
import com.yonyougov.devops.cruxcisupport.deployhistory.enums.DeployHisOperationTypeEnum;
import com.yonyougov.devops.cruxcisupport.domain.DomainEntity;
import com.yonyougov.devops.cruxcisupport.domain.DomainService;
import com.yonyougov.devops.cruxcisupport.enums.EnvInfoTypeEnum;
import com.yonyougov.devops.cruxcisupport.enums.EnvTypeEnum;
import com.yonyougov.devops.cruxcisupport.env.EnvService;
import com.yonyougov.devops.cruxcisupport.envconfigitem.EnvConfigItemEntity;
import com.yonyougov.devops.cruxcisupport.envconfigitem.EnvConfigItemService;
import com.yonyougov.devops.cruxcisupport.k8s.Constant;
import com.yonyougov.devops.cruxcisupport.k8s.K8sRepository;
import com.yonyougov.devops.cruxcisupport.k8s.configmap.ConfigMapRepository;
import com.yonyougov.devops.cruxcisupport.k8s.configmap.ConfigMapRequest;
import com.yonyougov.devops.cruxcisupport.k8s.deployment.*;
import com.yonyougov.devops.cruxcisupport.k8s.error.K8sConfIgNotFindException;
import com.yonyougov.devops.cruxcisupport.k8s.event.EventRepository;
import com.yonyougov.devops.cruxcisupport.k8s.log.LoggerRepository;
import com.yonyougov.devops.cruxcisupport.k8s.pod.PodRepository;
import com.yonyougov.devops.cruxcisupport.k8s.pod.PodStatusInfo;
import com.yonyougov.devops.cruxcisupport.k8s.rest.*;
import com.yonyougov.devops.cruxcisupport.k8s.rest.service.K8sDeploymentService;
import com.yonyougov.devops.cruxcisupport.k8s.rest.thread.DeployHisRecordThread;
import com.yonyougov.devops.cruxcisupport.k8s.rest.vo.PodEventVo;
import com.yonyougov.devops.cruxcisupport.k8s.secret.SecretRepository;
import com.yonyougov.devops.cruxcisupport.k8s.secret.SecretRequest;
import com.yonyougov.devops.cruxcisupport.k8s.service.ServicePortInfo;
import com.yonyougov.devops.cruxcisupport.k8s.service.ServiceRepository;
import com.yonyougov.devops.cruxcisupport.k8s.service.ServiceRequest;
import com.yonyougov.devops.cruxcisupport.lifecycle.LifecycleEntity;
import com.yonyougov.devops.cruxcisupport.lifecycle.LifecycleService;
import com.yonyougov.devops.cruxcisupport.user.UserService;
import com.yonyougov.devops.cruxcisupport.version.VersionEntity;
import com.yonyougov.devops.cruxcisupport.version.VersionService;
import com.yonyougov.devops.fwc.core.entity.Pagination;
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.Event;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.KubernetesClient;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class K8sDeploymentServiceImpl implements K8sDeploymentService {
    @Autowired
    private K8sRepository k8sRepository;
    @Autowired
    private ConfigMapRepository configMapRepository;
    @Autowired
    private SecretRepository secretRepository;
    @Autowired
    private EnvConfigItemService envConfigItemService;
    @Autowired
    private DeploymentRepository deploymentRepository;
    @Autowired
    private LoggerRepository loggerRepository;
    @Autowired
    private DomainService domainService;
    @Autowired
    private PodRepository podRepository;
    @Autowired
    private AppService appService;
    @Autowired
    private ServiceRepository serviceRepository;
    @Autowired
    private AppEnvPortService appEnvPortService;
    @Autowired
    private AppEnvConfigItemService appEnvConfigItemService;
    @Autowired
    private DeployHisService deployHisService;
    @Autowired
    private UserService userService;
    @Autowired
    private VersionService versionService;
    @Autowired
    private EventRepository eventRepository;
    @Autowired
    private EnvService envService;
    @Value("${imagePrefix}")
    private String IMAGEPREFIX;
    @Value("${k8s.dockerconfigjson}")
    private String DOCKERCONFIGJSON;
    @Autowired
    private AppEnvService appEnvService;
    @Autowired
    private LifecycleService lifecycleService;


    /**
     * 重建configMap
     * @param envId
     * @param envName
     * @throws Exception
     */
    @Override
    @SneakyThrows
    public void createConfigMap(BigInteger envId, String envName) throws Exception{
        EnvConfigItemEntity param = new EnvConfigItemEntity();
        param.setEnvId(envId);
        param.setPageAble(false);
        List<EnvConfigItemEntity> datas = envConfigItemService.findAllBy(param).getDatas();
        Map<String,String> configMapDatas = new HashMap<>();
        datas.forEach(envConfigItemEntity -> configMapDatas.put(envConfigItemEntity.getKey(),envConfigItemEntity.getValue()));
        ConfigMapRequest configMapRequest = ConfigMapRequest.builder().
                nameSpace(envName).
                name(Constant.CONFIG_MAP_NAME).
                datas(configMapDatas).
                build();
        configMapRepository.createOrReplace(configMapRequest);
    }

    public List<EnvInfo> createEnvContext(BigInteger appEnvId, BigInteger envId,BigInteger appId, String envName) throws Exception {
        List<EnvInfo> envInfos = new ArrayList<>();
        // configMap中的特殊环境变量
        EnvConfigItemEntity envConfigItemEntity = new EnvConfigItemEntity();
        envConfigItemEntity.setEnvId(envId);
        envConfigItemEntity.setPageAble(false);
        List<EnvConfigItemEntity> datas = envConfigItemService.findAllBy(envConfigItemEntity).getDatas();
        for (EnvConfigItemEntity data : datas) {
            if(data.getType() != null && !data.getType().equals(EnvInfoTypeEnum.defaultString.getCode())){
                envInfos.add(EnvInfo.builder()
                        .name(data.getKey())
                        .value(data.getValue())
                        .envInfoType(data.getType())
                        .absolutePath(data.getAbsolutePath())
                        .build());
            }

        }

        //用户自定义的环境环境变量
        AppEnvConfigItemEntity param = new AppEnvConfigItemEntity();
        param.setAppEnvId(appEnvId);
        param.setPageAble(false);
        List<AppEnvConfigItemEntity> appEnvConfigItemEntityList = appEnvConfigItemService.findAllBy(param).getDatas();

        if(null != appEnvConfigItemEntityList && appEnvConfigItemEntityList.size() > 0){
            List<EnvInfo> needAddEnvInfos = appEnvConfigItemEntityList.stream().map(appEnvConfigItemEntity ->
                    EnvInfo.builder()
                            .name(appEnvConfigItemEntity.getName())
                            .value(appEnvConfigItemEntity.getValue())
                            .envInfoType(0)
                            .build()).collect(Collectors.toList());
            envInfos.addAll(needAddEnvInfos);
        }

        //默认注入的环境变量
        /**
         * 1. 判断是否为开发模式
         *   1.1 开发模式注入主节点ip+映射出的http端口
         *   1.2 测试模式注入podip+应用http端口
         */
        EnvInfo envInfo = new EnvInfo();
        Integer envTypeCode = envService.justEnvType(envId);
        if(EnvTypeEnum.DEV.equals(envTypeCode)){
            String ip = envService.getMasterip(envId);
            Integer port = appEnvService.getServiceOutPort(appEnvId);
            envInfo.setName("k8s.ip");
            envInfo.setValue(ip);
            envInfos.add(envInfo);
            envInfo = new EnvInfo();
            envInfo.setName("k8s.port");
            envInfo.setValue(port.toString());
            envInfos.add(envInfo);
        }else if(EnvTypeEnum.TEST.equals(envTypeCode)){
            String ip = "status.podIP";
            Integer port = -1;
            AppEntity appEntity = appService.findBy(AppEntity.builder().id(appId).build());
            if (appEntity.getExposePort()) {
                port = appEnvService.getServiceInnerPort(appEnvId);
            }else{
                port = appService.getServiceInnerPort(appId);
            }
            envInfo.setName("k8s.ip");
            envInfo.setValue(ip);
            envInfo.setEnvInfoType(EnvInfoTypeEnum.metadateRef.getCode());
            envInfos.add(envInfo);
            envInfo = new EnvInfo();
            envInfo.setName("k8s.port");
            envInfo.setValue(port.toString());
            envInfos.add(envInfo);
        }
        // 注入引入k8s元数据的环境变量
        envInfo = new EnvInfo();
        envInfo.setName("POD_IP");
        envInfo.setValue("status.podIP");
        envInfo.setEnvInfoType(EnvInfoTypeEnum.metadateRef.getCode());
        envInfos.add(envInfo);
        envInfo = new EnvInfo();
        envInfo.setName("POD_NAME");
        envInfo.setValue("metadata.name");
        envInfo.setEnvInfoType(EnvInfoTypeEnum.metadateRef.getCode());
        envInfos.add(envInfo);
        envInfo = new EnvInfo();
        envInfo.setName("POD_NAMESPACE");
        envInfo.setValue("metadata.namespace");
        envInfo.setEnvInfoType(EnvInfoTypeEnum.metadateRef.getCode());
        envInfos.add(envInfo);
        // 普罗米修斯监控路径

        envInfo = new EnvInfo();
        AppEntity appEntity = new AppEntity();
        appEntity.setId(appId);
        String actuatorUrl= appService.get(appId).getActuatorUrl();
        envInfo.setName("ACTUATOR_URL");
        envInfo.setValue(actuatorUrl);
        envInfos.add(envInfo);
        return envInfos;
    }

    public HealthCheckContext createHealthcheckContext(BigInteger appId){
        //获取健康检查信息
        HealthCheckContext healthCheckContext = null;
        AppEntity app = new AppEntity();
        app.setId(appId);
        AppEntity appEntity = appService.findBy(app);
        if(StringUtils.isNotBlank(appEntity.getHealthCheckPath())){
            healthCheckContext = new HealthCheckContext();
            log.info(String.format("健康检查路径为%s",appEntity.getHealthCheckPath()).toString());
            healthCheckContext.setPath(appEntity.getHealthCheckPath());
            healthCheckContext.setPort(appEntity.getHealthCheckPort());
        }
        return healthCheckContext;
    }

    public LifecycleContext createLifecycleContext(){
        LifecycleEntity build = LifecycleEntity.builder().build();
        build.setPageAble(false);
        List<LifecycleEntity> datas = lifecycleService.findAllBy(build).getDatas();
        if(CollectionUtils.isNotEmpty(datas)){
            return LifecycleContext.builder().startPostScript(datas.get(0).getStartScript()).endPreScript(datas.get(0).getEndScript()).build();
        }
        return null;
    }

    @Override
    public void createDeployment(DeploymentRestRequest request) throws Exception {
        //判断configMap 和 secret 是否创建
        ConfigMap configMap = configMapRepository.get(Constant.CONFIG_MAP_NAME, request.getEnvName());
        if(null == configMap || configMap.getData().size() == 0){
            createConfigMap(request.getEnvId(),request.getEnvName());
        }
        Secret secret = secretRepository.get(Constant.SECRET_NAME, request.getEnvName());
        if(null == secret || secret.getData().size() == 0){
            Map<String,String> secretMapDatas = new HashMap<>();
            secretMapDatas.put(Constant.SECRET_MAP_KEY,DOCKERCONFIGJSON);
            SecretRequest secretRequest = SecretRequest.builder().
                    name(Constant.SECRET_NAME).
                    nameSpace(request.getEnvName()).
                    data(secretMapDatas).
                    build();
            secretRepository.createOrReplace(secretRequest);
        }
        String appDomain = appService.findBy(AppEntity.builder().app(request.getAppName()).build()).getDomain();
        String domain = "crux";
        if(ObjectUtils.isNotEmpty(domainService.findBy(DomainEntity.builder().name(appDomain).build()))){
            domain = domainService.findBy(DomainEntity.builder().name(appDomain).build()).getCode();
        }
        //创建Deployment
        Map<String,String> deploymentLabelMapDatas = new HashMap<>();
        deploymentLabelMapDatas.put("app",request.getAppName());
        Map<String,String> deploymentMatchLabelMapDatas = new HashMap<>();
        deploymentMatchLabelMapDatas.put("app",request.getAppName());
        DeploymentRequest deploymentRequest = DeploymentRequest.builder()
                .nameSpace(request.getEnvName())
                .containerName(request.getAppName())
                .metadataName(request.getAppName())
                .replicas(request.getReplicas())
                .labels(deploymentLabelMapDatas)
                .matchLabels(deploymentMatchLabelMapDatas)
                .imageName(StringUtils.isBlank(request.getImageName()) ? IMAGEPREFIX +  domain   +"/"+ request.getAppName() + ":" + request.getPackageVersion():request.getImageName())
                .envContext(createEnvContext(request.getAppEnvId(),request.getEnvId(),request.getAppId(),request.getEnvName()))
                .healthCheckContext(createHealthcheckContext(request.getAppId()))
                .lifecycleContext(createLifecycleContext())
                .build();
        deploymentRepository.createOrReplace(deploymentRequest);

        //创建Service
        AppEnvPortEntity appEnvPortParam = new AppEnvPortEntity();
        appEnvPortParam.setAppEnvId(request.getAppEnvId());
        appEnvPortParam.setPageAble(false);
        List<AppEnvPortEntity> appEnvPortEntityList = appEnvPortService.findAllBy(appEnvPortParam).getDatas();
        if(null == appEnvPortEntityList || appEnvPortEntityList.size() == 0){
            log.info("没有端口映射信息,不需要创建service");
        }else{
            Map<String,String> serviceLabelMap = new HashMap<>();
            serviceLabelMap.put("app",request.getAppName());

            List<ServicePortInfo> servicePortInfos = appEnvPortEntityList.stream().map(appEnvPortEntity -> {
                ServicePortInfo servicePortInfo = new ServicePortInfo();
                servicePortInfo.setName(appEnvPortEntity.getName());
                servicePortInfo.setProtocol("TCP");
                servicePortInfo.setPort(appEnvPortEntity.getPort());
                servicePortInfo.setTargetPort(appEnvPortEntity.getPort());
                servicePortInfo.setNodePort(appEnvPortEntity.getMappingPort());
                return servicePortInfo;
            }).collect(Collectors.toList());

            ServiceRequest serviceRequest = ServiceRequest.builder()
                    .nameSpace(request.getEnvName())
                    .serviceName(request.getAppName() + "-svc")
                    .selectors(serviceLabelMap)
                    .servicePortInfos(servicePortInfos)
                    .build();
            serviceRepository.createOrReplace(serviceRequest);
        }
    }

    @Override
    public void batchCreateDeployment(BatchDeploymentRestRequest request)  throws Exception {
        int flag = 0;
        String msg = "";
        for (DeploymentRestRequest deploymentRestRequest : request.getDeploymentRestRequestList()) {
            //判断版本是否存在
            if (StringUtils.isNotBlank(deploymentRestRequest.getPackageVersion())) {
                VersionEntity versionEntity = versionService.findBy(VersionEntity.builder()
                        .versionName(deploymentRestRequest.getPackageVersion())
                        .appId(deploymentRestRequest.getAppId())
                        .build());
                if (null == versionEntity) {
                    msg = "应用: " + deploymentRestRequest.getAppName() + "的版本: " + deploymentRestRequest.getPackageVersion() + " 不存在,请添加版本";
                    flag = 1;
                    break;
                }
            }else if (StringUtils.isBlank(deploymentRestRequest.getImageName())) {
                msg = "应用: " + deploymentRestRequest.getAppName() + "的镜像: " + deploymentRestRequest.getImageName() + " 不存在,请检查部署信息";
                flag = 2;
                break;
            }
            // 必须有服务端口，且如果应用设置了暴漏端口，必须映射出去
            appEnvService.deployCheck(deploymentRestRequest.getAppEnvId());
        }
        if (flag == 0) {
            for (DeploymentRestRequest deploymentRestRequest : request.getDeploymentRestRequestList()) {
                createDeployment(deploymentRestRequest);
            }
        } else {
            throw new Exception(msg);
        }


        // 记录部署历史
        List<DeployHisRecordDto> deployHisRecordDtoList = request.getDeploymentRestRequestList().stream().map(e -> {
            return DeployHisRecordDto.builder()
                    .envName(e.getEnvName())
                    .appName(e.getAppName())
                    .deployHisOperationTypeEnum(DeployHisOperationTypeEnum.DEPLOY)
                    .build();
        }).collect(Collectors.toList());
        DeployHisRecordThread deployHisInsertThread = new DeployHisRecordThread(appEnvService, versionService, userService, deployHisService, deployHisRecordDtoList);
        deployHisInsertThread.start();
    }

    @Override
    public String getPodLog(PodLogRestRequest request) throws Exception {
        String flag = request.getFlag();
        Integer seconds = request.getSeconds();
        if(null == seconds){
            seconds = 1;
        }
        if(K8sLogWayEnum.SINCE_TIME_WAY.getValue().equals(flag)){
            return loggerRepository.getPodLog(request.getEnvName(),request.getAppName(),seconds);
        }else{
            return  loggerRepository.getPodLogByTail(request.getEnvName(),request.getAppName(),seconds);
        }
    }

    @Override
    public List<String> getPodNames(PodNameRestRequest request) throws K8sConfIgNotFindException {
        return podRepository.getPodNames(request.getEnvName(),request.getAppName());
    }

    @Override
    public List<PodStatusInfo> getPodDetailsByNameSpace(PodDetailsRestRequest request) throws K8sConfIgNotFindException {
        return podRepository.getAllPodStatusInfo(request.getEnvName());
    }

    @Override
    public boolean deleteBatchPod(PodBatchDelRestRequest request) throws K8sConfIgNotFindException {
        String envName = request.getEnvName();
        List<String> appNames = request.getAppNames();
        log.debug("method[deleteBatchPod]: envName is {}", envName);
        log.debug("method[deleteBatchPod]: appNames is {}", appNames);
        KubernetesClient client = k8sRepository.createDefaultClient(envName);
        for (String appName : appNames) {
            deploymentRepository.delete(client, envName, appName);
            serviceRepository.delete(client, envName, appName + "-svc");
        }
        // 记录部署历史
        List<DeployHisRecordDto> deployHisRecordDtoList = request.getAppNames().stream().map(e -> {
            return DeployHisRecordDto.builder()
                    .envName(request.getEnvName())
                    .appName(e)
                    .deployHisOperationTypeEnum(DeployHisOperationTypeEnum.KILL)
                    .build();
        }).collect(Collectors.toList());
        DeployHisRecordThread deployHisInsertThread = new DeployHisRecordThread(appEnvService, versionService, userService, deployHisService, deployHisRecordDtoList);
        deployHisInsertThread.start();
        return true;
    }

    @Override
    public List<PodEventVo> getEventsByPodName(PodEventRestRequest request) throws K8sConfIgNotFindException {
        List<Event> eventList = eventRepository.getEventsByPodName(request.getEnvName(), request.getAppName());
        if (null == eventList) {
            return null;
        }
        return eventList.stream().map(event -> PodEventVo.builder()
                .reason(event.getReason())
                .type(event.getType())
                .message(event.getMessage())
                .build()).collect(Collectors.toList());
    }

    @Override
    public String findFristDeploy(String env, String... apps) throws K8sConfIgNotFindException {
        KubernetesClient client = k8sRepository.createDefaultClient(env);
        List<Pod> items = client.pods().inNamespace(env).list().getItems();
        Optional<String> reduce = items.stream().map(e -> e.getMetadata().getName()).reduce((a1, a2) -> a1 + a2);
        if (reduce.isPresent()) {
            for (String app : apps) {
                if (reduce.get().contains(app)) {
                    return app;
                }
            }
        }
        return "";
    }
}
